/*
 * Netflix API Basics
 * Copyright (c) 2009 Dave Cook Consulting, LLC
 *                    http://www.netdave.com
 * 
 * This code released under the Code Project Open License
 * http://www.codeproject.com/info/cpol10.aspx
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;

namespace NetflixCatalogExplorer
{
	#region Netflix Catalog Title Data Structures
	/// <summary>
	/// Short and full titles
	/// </summary>
	public struct Title
	{
		public string shortTitle;
		public string regularTitle;
	}
	/// <summary>
	/// URLs to title cover images
	/// </summary>
	public struct BoxArt
	{
		public string smallBox;
		public string mediumBox;
		public string largeBox;
	}
	/// <summary>
	/// Category information
	/// </summary>
	public class Category
	{
		public string scheme;
		public string label;
		public string term;
	}
	/// <summary>
	/// Links to expanded information
	/// </summary>
	public class Link
	{
		public string href;
		public string rel;
		public string title;
	}
	/// <summary>
	/// General purpose container for title information
	/// </summary>
	public class CatalogTitle
	{
		public int rank;
		public string idUrl;	// fully qualified URL to retrieve title details
		public string ID;		// title ID stripped from idUrl
		public Title title;
		public BoxArt cover;
		public string year;
		public string runtime;
		public string avg_rating;
		public List<Category> categories;
		public Dictionary<string, Link> links;
		// the following are for title expansion
		public string synopsis;
		public Dictionary<string, Link> cast;
		public Dictionary<string, Link> directors;
	}
	#endregion Netflix Catalog Title Data Structures

	class NetflixParser
	{
		/// <summary>
		/// Parses the title information returned from a search request
		/// </summary>
		/// <param name="xDoc">XmlDocument object containing data to parse.</param>
		/// <returns>List of CatalogTitle objects parsed.</returns>
		public List<CatalogTitle> ParseSearchResults(XmlDocument xDoc)
		{
			List<CatalogTitle> _titleList = new List<CatalogTitle>();

			XmlNode xNode = xDoc.DocumentElement;
			if (xNode.Name != "catalog_titles" || !xNode.HasChildNodes)
				return null;	// not a catalog search result document

			int rank = 0;
			foreach (XmlNode subNode in xNode)
			{
				if (subNode.Name == "catalog_title" && subNode.HasChildNodes)
				{
					CatalogTitle title = ParseCatalogTitle(subNode);
					if (title != null)
					{
						title.rank = rank++;
						_titleList.Add(title);
					}
				}
			}
			return _titleList;
		}

		/// <summary>
		/// Parses the title data returned from a title detail request
		/// </summary>
		/// <param name="xDoc">XmlDocument object containing data to parse.</param>
		/// <returns>CatalogTitle object parsed.</returns>
		public CatalogTitle ParseTitleInfo(XmlDocument xDoc)
		{
			XmlNode xNode = xDoc.DocumentElement;
			// the node must be a catalog_title
			if (xNode.Name != "catalog_title" || !xNode.HasChildNodes)
				return null;
			return ParseCatalogTitle(xNode);
		}

		#region Parsing Methods
		// parses a single catalog title
		private CatalogTitle ParseCatalogTitle(XmlNode xNode)
		{
			CatalogTitle title = new CatalogTitle();
			foreach (XmlNode subNode in xNode.ChildNodes)
			{
				ParseTitleElement(subNode, title);
			}
			return title;
		}

		// parses the elements into a CatalogTitle object
		private void ParseTitleElement(XmlNode xNode, CatalogTitle title)
		{
			// for single element items, data is contained in a #text child node,
			// so we get it from the .FirstChild value
			switch (xNode.Name)
			{
				case "id":
					title.idUrl = xNode.FirstChild.Value;
					string[] parts = title.idUrl.Split('/');
					title.ID = parts[parts.GetLength(0) - 1];
					break;
				case "title":
					title.title.shortTitle = xNode.Attributes["short"].Value;
					title.title.regularTitle = xNode.Attributes["regular"].Value;
					break;
				case "box_art":
					title.cover.smallBox = xNode.Attributes["small"].Value;
					title.cover.mediumBox = xNode.Attributes["medium"].Value;
					title.cover.largeBox = xNode.Attributes["large"].Value;
					break;
				case "release_year":
					title.year = xNode.FirstChild.Value;
					break;
				case "runtime":
					title.runtime = xNode.FirstChild.Value;
					break;
				case "average_rating":
					title.avg_rating = xNode.FirstChild.Value;
					break;
				case "category":
					Category cat = new Category();
					cat.scheme = xNode.Attributes["scheme"].Value;
					cat.label = xNode.Attributes["label"].Value;
					cat.term = xNode.Attributes["term"].Value;
					if (title.categories == null)
						title.categories = new List<Category>();
					title.categories.Add(cat);
					break;
				case "link":
					Link lnk = ParseLink(xNode);
					if (lnk == null)
						break;
					if (title.links == null)
						title.links = new Dictionary<string, Link>();
					title.links.Add(lnk.title, lnk);
					// if title expansion is requested, a link can carry additional information
					if (xNode.HasChildNodes)
					{
						ParseExpansion(xNode, lnk.title, title);
					}
					break;
				default:
					break;
			}
		}

		// parses a single Link object node
		private Link ParseLink(XmlNode xNode)
		{
			if (xNode.Name != "link")
				return null;
			Link lnk = new Link();
			lnk.href = xNode.Attributes["href"].Value;
			lnk.rel = xNode.Attributes["rel"].Value;
			lnk.title = xNode.Attributes["title"].Value;
			return lnk;
		}

		// parses the contents of an expanded link node
		// note that not all expansions are supported here,
		// these are just a few examples for the Codeproject article
		private void ParseExpansion(XmlNode xNode, string group, CatalogTitle title)
		{
			XmlNode subNode = xNode.FirstChild;
			switch (subNode.Name)
			{
				case "synopsis":
					title.synopsis = subNode.FirstChild.Value;
					break;

				case "people":
					foreach (XmlNode node in subNode.ChildNodes)
					{
						Link lnk = ParseLink(node);
						if (lnk == null)
							break;
						switch (group)
						{
							case "cast":
								if (title.cast == null)
									title.cast = new Dictionary<string, Link>();
								title.cast.Add(lnk.title, lnk);
								break;
							case "directors":
								if (title.directors == null)
									title.directors = new Dictionary<string, Link>();
								title.directors.Add(lnk.title, lnk);
								break;
						}
					}
					break;
			}
		}
		#endregion Parsing Methods
	}
}
